﻿using DataCheck = MicroCloud.Utils.Check;

namespace MicroCloud.SMS
{
    #region "业务实现：短信验证码服务基类"
    /// <summary>
    /// 业务实现：短信验证码服务基类
    /// </summary>
    /// <typeparam name="TVerifyCode">验证码短信记录实体类型</typeparam>
    /// <typeparam name="TVerifyCodeKey">验证码短信记录实体主键类型</typeparam>
    /// <typeparam name="IVerifyCodeEventData">短信验证码发送事件数据类型</typeparam>
    public abstract class SmsVerifyCodeServiceBase<TVerifyCode, TVerifyCodeKey, IVerifyCodeEventData> : ISmsVerifyCodeService
        where TVerifyCode : VerifyCodeBase<TVerifyCodeKey>, new()
        where TVerifyCodeKey : IEquatable<TVerifyCodeKey>
        where IVerifyCodeEventData : SmsVerifyCodeEventDataBase<TVerifyCode, TVerifyCodeKey>, new()
    {
        #region 属性
        #region "获取 服务提供者对象"
        /// <summary>
        /// 获取 服务提供者对象
        /// </summary>
        protected IServiceProvider ServiceProvider { get; }
        #endregion
        #region "获取 事件总线"
        /// <summary>
        /// 获取 事件总线
        /// </summary>
        protected IEventBus EventBus => ServiceProvider.GetService<IEventBus>();
        #endregion
        #region "获取 日志对象"
        /// <summary>
        /// 获取 日志对象
        /// </summary>
        protected ILogger Logger { get; }
        #endregion
        #region "获取 短信配置选项"
        /// <summary>
        /// 获取 短信配置选项
        /// </summary>
        protected SmsOptions SmsOpt => ServiceProvider.GetService<IConfiguration>()?.GetSmsOptions();
        #endregion

        #region "获取 短信发送功能"
        /// <summary>
        /// 获取 短信发送功能
        /// </summary>
        protected ISmsSender SmsSender => ServiceProvider.GetService<ISmsSender>();
        #endregion
        #region "获取 短信查询功能"
        /// <summary>
        /// 获取 短信查询功能
        /// </summary>
        protected ISmsInquirer SmsInquirer => ServiceProvider.GetService<ISmsInquirer>();
        #endregion

        #region "获取 验证码短信仓储对象"
        /// <summary>
        /// 获取 验证码短信仓储对象
        /// </summary>
        protected IRepository<TVerifyCode, TVerifyCodeKey> VerifyCodeRepository => ServiceProvider.GetService<IRepository<TVerifyCode, TVerifyCodeKey>>();
        #endregion
        #region "获取 验证码短信查询数据集"
        /// <summary>
        /// 获取 验证码短信查询数据集
        /// </summary>
        protected IQueryable<TVerifyCode> VerifyCodes => VerifyCodeRepository.QueryAsNoTracking();
        #endregion

        #endregion

        #region "构造函数"
        #region "初始化一个短信验证码服务基类的新实例"
        /// <summary>
        /// 初始化一个短信验证码服务基类 <see cref="SmsVerifyCodeServiceBase{TVerifyCode, TVerifyCodeKey, ISendEventData}"/> 的新实例
        /// </summary>
        /// <param name="provider">服务提供者</param>
        public SmsVerifyCodeServiceBase(IServiceProvider provider)
        {
            ServiceProvider = provider;
            Logger = provider.GetLogger(GetType());
        }
        #endregion

        #endregion

        #region "同步方法"
        #region "发送验证码短信"
        /// <summary>
        /// 发送验证码短信
        /// </summary>
        /// <param name="codeLength">验证码长度</param>
        /// <param name="codeType">验证码类型</param>
        /// <param name="requestIp">请求ID</param>
        /// <param name="phoneNumber">接收短信的手机号码</param>
        /// <returns>业务操作结果</returns>
        public virtual OperationResult Send(int codeLength, ValidateCodeType codeType, string requestIp, string phoneNumber)
        {
            DataCheck.NotNullOrEmpty(phoneNumber, nameof(phoneNumber));
            codeLength = codeLength <= 0 ? 6 : codeLength;

            //设置验证码
            ValidateCoder coder = new()
            {
                RandomColor = true,
                RandomItalic = true,
                RandomLineCount = 7,
                RandomPointPercent = 10,
                RandomPosition = true
            };
            var verifyCode = coder.GetCode(codeLength, codeType);
            //设置常规参数
            var currentDate = DateTime.Now.Date;
            var expireMins = SmsOpt.VerifyCodeExpireMins;
            var limitOfNumberDay = SmsOpt.LimitOfNumberDay;

            //验证短信发送限制
            var sentRecords = VerifyCodes.Count(o => o.SendDate.Date == currentDate.Date && o.PhoneNumber == phoneNumber);
            if (sentRecords >= limitOfNumberDay)
            {
                return OperationResult.Error(I18N.T("发送验证码短信失败：手机号 {0} 发送次数单日上限不能超过{1}次。", phoneNumber, limitOfNumberDay));
            }

            //设置发送参数
            var verifyCodeTemplateParam = SmsOpt.VerifyCodeTempParam.FormatWith(verifyCode);
            SendParamsDto sendParamsDto = new()
            {
                SmsType = SmsType.VerifyCode,
                TemplateParam = verifyCodeTemplateParam,
                MsgContent = SmsOpt.VerifyCodeSMS.FormatWith(verifyCode, expireMins),// $"尊敬的用户您好，您的验证码：{verifyCode}，{expireMins}分钟有效，请及时操作，祝您生活愉快！",
            };
            //发送短信 OperationResult<SmsSentResponse>
            var sentOperatResult = SmsSender.SendAsync(sendParamsDto, phoneNumber).Result;
            if (!sentOperatResult.Succeeded)
            {
                return sentOperatResult.ToOperationResult();
            }
            string templateCode = "", outId = "", msgContent = sendParamsDto.MsgContent;
            DateTime receiveDate = DateTime.Now, sendDate = DateTime.Now;
            if (SmsOpt.ProviderEnum == SmsProvider.Aliyun)
            {
                //为保证服务商数据已持久化可供查询，建议线程睡眠3秒后继续执行下步操作
                Thread.Sleep(3000);
                //设置查询参数
                QueryParamsDto queryParamsDto = new()
                {
                    PhoneNumber = phoneNumber,
                    SendDate = currentDate,
                    PageSize = 10,
                    CurrentPage = 1,
                    BizId = sentOperatResult.Data.BizId,
                };
                //查询短信 OperationResult<SmsQueryResponse>
                var queryOperatResult = SmsInquirer.QuerySentDetailsAsync(queryParamsDto).Result;
                if (!queryOperatResult.Succeeded)
                {
                    return queryOperatResult.ToOperationResult();
                }
                var sentDetail = queryOperatResult.Data.SendDetailDtos.FirstOrDefault();
                if (sentDetail != null)
                {
                    while (sentDetail.SendStatus == SmsSendStatus.Waiting)
                    {
                        Thread.Sleep(3000);
                        queryOperatResult = SmsInquirer.QuerySentDetailsAsync(queryParamsDto).Result;
                        sentDetail = queryOperatResult.Data.SendDetailDtos.FirstOrDefault();
                    }
                    if (!sentDetail.Succeeded)
                    {
                        return OperationResult.Error((object)queryOperatResult.Data, queryOperatResult.Data.Message);
                    }

                    templateCode = sentDetail.TemplateCode;
                    outId = sentDetail.OutId;
                    phoneNumber = sentDetail.PhoneNum;
                    receiveDate = Convert.ToDateTime(sentDetail.ReceiveDate);
                    sendDate = Convert.ToDateTime(sentDetail.SendDate);
                    msgContent = sentDetail.Content;
                }
            }

            //构建存储参数，持久化短信数据
            var entity = new TVerifyCode
            {
                Provider = SmsOpt.ProviderEnum,
                SignName = SmsOpt.SignName,
                TemplateCode = templateCode,
                BizId = sentOperatResult.Data.BizId,
                RequestId = requestIp,
                OutId = outId,
                PhoneNumber = phoneNumber,
                ReceiveDate = receiveDate,
                SendDate = sendDate,
                Message = msgContent,
                VerifyCode = verifyCode,
                ExpireMins = expireMins,
                Verified = false,
                VerifiedTime = null,
            };
            var operationResult = VerifyCodeRepository.Insert(entity);
            //触发发送成功事件
            if (operationResult.Succeeded)
            {
                IVerifyCodeEventData eventData = new()
                {
                    OperationType = SmsOperationType.Send,
                    Entities = new TVerifyCode[] { entity },
                };
                EventBus?.Publish(this, eventData);

                var dataStr = entity.Id.ToString();

                return OperationResult.Success((object)dataStr);
            }

            return operationResult;
        }
        #endregion
        #region "校验短信验证码有效性"
        /// <summary>
        /// 校验短信验证码有效性
        /// </summary>
        /// <param name="dto">短信验证码DTO</param>
        /// <param name="confirmIfSuccess">验证成功时是否确认</param>
        /// <returns></returns>
        public virtual OperationResult Check(VerifyCodeCheckDto dto, bool confirmIfSuccess = true)
        {
            DataCheck.NotNull(dto, nameof(dto));
            DataCheck.NotNullOrEmpty(dto.Code, nameof(dto.Code));
            DataCheck.NotNullOrEmpty(dto.CodeId, nameof(dto.CodeId));
            DataCheck.NotNullOrEmpty(dto.PhoneNumber, nameof(dto.PhoneNumber));

            //dto.BizId = dto.BizId.ToUrlDecode();
            //var tmpBizId = dto.BizId.Split(Separator);
            //var providerVal = (SmsProvider)Convert.ToInt32(tmpBizId[0]);
            //var bizIdVal = tmpBizId[1];
            var entity = VerifyCodes.Where(o => o.Id.Equals(dto.CodeId.CastTo<TVerifyCodeKey>()) && o.VerifyCode.Equals(dto.Code) && o.PhoneNumber.Equals(dto.PhoneNumber) && !o.Verified).OrderByDescending(p => p.SendDate).FirstOrDefault();
            if (entity == null)
            {
                return OperationResult.Error(I18N.T("未找到有效的验证码信息，请确认是否已发送成功或者已被验证。"));
            }
            //设置常规参数
            var receiveDate = entity.ReceiveDate;
            var expireMins = entity.ExpireMins;
            //判断验证码是否过期
            if (receiveDate.IsExprired(expireMins.ToDouble(), DurationType.Minutes))
            {
                return OperationResult.Error(I18N.T("验证码已过期"));
            }
            //验证成功时是否确认
            if (confirmIfSuccess)
            {
                entity.Verified = true;
                entity.VerifiedTime = DateTime.Now;
                var operationResult = VerifyCodeRepository.Update(entity);
                if (!operationResult.Succeeded)
                {
                    return operationResult;
                }
                //触发确认成功事件
                IVerifyCodeEventData eventData = new()
                {
                    OperationType = SmsOperationType.Confirm,
                    Entities = new TVerifyCode[] { entity },
                };
                EventBus?.Publish(this, eventData);
            }

            return OperationResult.Success();
        }
        #endregion

        #endregion

        #region "异步方法"
        #region "发送验证码短信"
        /// <summary>
        /// 发送验证码短信
        /// </summary>
        /// <param name="codeLength">验证码长度</param>
        /// <param name="codeType">验证码类型</param>
        /// <param name="requestIp">请求ID</param>
        /// <param name="phoneNumber">接收短信的手机号码</param>
        /// <returns>业务操作结果</returns>
        public virtual async Task<OperationResult> SendAsync(int codeLength, ValidateCodeType codeType, string requestIp, string phoneNumber)
        {
            DataCheck.NotNullOrEmpty(phoneNumber, nameof(phoneNumber));
            codeLength = codeLength <= 0 ? 6 : codeLength;

            //设置验证码
            ValidateCoder coder = new()
            {
                RandomColor = true,
                RandomItalic = true,
                RandomLineCount = 7,
                RandomPointPercent = 10,
                RandomPosition = true
            };
            var verifyCode = coder.GetCode(codeLength, codeType);
            //设置常规参数
            var currentDate = DateTime.Now.Date;
            var expireMins = SmsOpt.VerifyCodeExpireMins;
            var limitOfNumberDay = SmsOpt.LimitOfNumberDay;

            //验证短信发送限制
            var sentRecords = VerifyCodes.Count(o => o.SendDate.Date == currentDate.Date && o.PhoneNumber == phoneNumber);
            if (sentRecords >= limitOfNumberDay)
            {
                return OperationResult.Error(I18N.T("发送验证码短信失败：手机号 {0} 发送次数单日上限不能超过{1}次。", phoneNumber, limitOfNumberDay));
            }

            //设置发送参数
            var verifyCodeTemplateParam = SmsOpt.VerifyCodeTempParam.FormatWith(verifyCode);
            SendParamsDto sendParamsDto = new()
            {
                SmsType = SmsType.VerifyCode,
                TemplateParam = verifyCodeTemplateParam,
                MsgContent = SmsOpt.VerifyCodeSMS.FormatWith(verifyCode, expireMins),// $"尊敬的用户您好，您的验证码：{verifyCode}，{expireMins}分钟有效，请及时操作，祝您生活愉快！",
            };
            //发送短信 OperationResult<SmsSentResponse>
            var sentOperatResult = await SmsSender.SendAsync(sendParamsDto, phoneNumber);
            if (!sentOperatResult.Succeeded)
            {
                return sentOperatResult.ToOperationResult();
            }
            string templateCode = "", outId = "", msgContent = sendParamsDto.MsgContent;
            DateTime receiveDate = DateTime.Now, sendDate = DateTime.Now;
            if (SmsOpt.ProviderEnum == SmsProvider.Aliyun)
            {
                //为保证服务商数据已持久化可供查询，建议线程睡眠3秒后继续执行下步操作
                Thread.Sleep(3000);
                //设置查询参数
                QueryParamsDto queryParamsDto = new()
                {
                    PhoneNumber = phoneNumber,
                    SendDate = currentDate,
                    PageSize = 10,
                    CurrentPage = 1,
                    BizId = sentOperatResult.Data.BizId,
                };
                //查询短信 OperationResult<SmsQueryResponse>
                var queryOperatResult = await SmsInquirer.QuerySentDetailsAsync(queryParamsDto);
                if (!queryOperatResult.Succeeded)
                {
                    return queryOperatResult.ToOperationResult();
                }
                var sentDetail = queryOperatResult.Data.SendDetailDtos.FirstOrDefault();
                if (sentDetail != null)
                {
                    while (sentDetail.SendStatus == SmsSendStatus.Waiting)
                    {
                        Thread.Sleep(3000);
                        queryOperatResult = await SmsInquirer.QuerySentDetailsAsync(queryParamsDto);
                        sentDetail = queryOperatResult.Data.SendDetailDtos.FirstOrDefault();
                    }
                    if (!sentDetail.Succeeded)
                    {
                        return OperationResult.Error((object)queryOperatResult.Data, queryOperatResult.Data.Message);
                    }

                    templateCode = sentDetail.TemplateCode;
                    outId = sentDetail.OutId;
                    phoneNumber = sentDetail.PhoneNum;
                    receiveDate = Convert.ToDateTime(sentDetail.ReceiveDate);
                    sendDate = Convert.ToDateTime(sentDetail.SendDate);
                    msgContent = sentDetail.Content;
                }
            }

            //构建存储参数，持久化短信数据
            var entity = new TVerifyCode
            {
                Provider = SmsOpt.ProviderEnum,
                SignName = SmsOpt.SignName,
                TemplateCode = templateCode,
                BizId = sentOperatResult.Data.BizId,
                RequestId = requestIp,
                OutId = outId,
                PhoneNumber = phoneNumber,
                ReceiveDate = receiveDate,
                SendDate = sendDate,
                Message = msgContent,
                VerifyCode = verifyCode,
                ExpireMins = expireMins,
                Verified = false,
                VerifiedTime = null,
            };
            var operationResult = await VerifyCodeRepository.InsertAsync(entity);
            //触发发送成功事件
            if (operationResult.Succeeded)
            {
                IVerifyCodeEventData eventData = new()
                {
                    OperationType = SmsOperationType.Send,
                    Entities = new TVerifyCode[] { entity },
                };
                await EventBus?.PublishAsync(this, eventData);

                var dataStr = entity.Id.ToString();

                return OperationResult.Success((object)dataStr);
            }
            return operationResult;
        }
        #endregion
        #region "校验短信验证码有效性"
        /// <summary>
        /// 校验短信验证码有效性
        /// </summary>
        /// <param name="dto">短信验证码DTO</param>
        /// <param name="confirmIfSuccess">验证成功时是否确认</param>
        /// <returns></returns>
        public virtual async Task<OperationResult> CheckAsync(VerifyCodeCheckDto dto, bool confirmIfSuccess = true)
        {
            DataCheck.NotNull(dto, nameof(dto));
            DataCheck.NotNullOrEmpty(dto.Code, nameof(dto.Code));
            DataCheck.NotNullOrEmpty(dto.CodeId, nameof(dto.CodeId));
            DataCheck.NotNullOrEmpty(dto.PhoneNumber, nameof(dto.PhoneNumber));

            //dto.BizId = dto.BizId.ToUrlDecode();
            //var tmpBizId = dto.BizId.Split(Separator);
            //var providerVal = (SmsProvider)Convert.ToInt32(tmpBizId[0]);
            //var bizIdVal = tmpBizId[1];
            var entity = VerifyCodes.Where(o => o.Id.Equals(dto.CodeId.CastTo<TVerifyCodeKey>()) && o.VerifyCode.Equals(dto.Code) && o.PhoneNumber.Equals(dto.PhoneNumber) && !o.Verified).OrderByDescending(p => p.SendDate).FirstOrDefault();
            if (entity == null)
            {
                return OperationResult.Error(I18N.T("未找到有效的验证码信息，请确认是否已发送成功或者已被验证。"));
            }
            //设置常规参数
            var receiveDate = entity.ReceiveDate;
            var expireMins = entity.ExpireMins;
            //判断验证码是否过期
            if (receiveDate.IsExprired(expireMins.ToDouble(), DurationType.Minutes))
            {
                return OperationResult.Error(I18N.T("验证码已过期"));
            }
            //验证成功时是否确认
            if (confirmIfSuccess)
            {
                entity.Verified = true;
                entity.VerifiedTime = DateTime.Now;
                var operationResult = await VerifyCodeRepository.UpdateAsync(entity);
                if (!operationResult.Succeeded)
                {
                    return operationResult;
                }
                //触发确认成功事件
                IVerifyCodeEventData eventData = new()
                {
                    OperationType = SmsOperationType.Confirm,
                    Entities = new TVerifyCode[] { entity },
                };
                EventBus?.PublishAsync(this, eventData);
            }
            return OperationResult.Success();
        }
        #endregion

        #endregion

    }
    #endregion

}
